package com.kinyx.framework.reflect;

import java.io.IOException;
import java.lang.annotation.Annotation;
import java.net.JarURLConnection;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLConnection;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.stream.Stream;

import com.kinyx.framework.Configs;
import com.kinyx.framework.annotation.assist.Tester;
import com.kinyx.framework.enums.impl.ErrorCodeX;
import com.kinyx.framework.exception.KRuntimeException;
import com.kinyx.framework.file.KPathUtils;
import com.kinyx.framework.log.Log;
import com.kinyx.framework.log.Logger;
import com.kinyx.framework.utils.KArrayUtils;

import lombok.AccessLevel;
import lombok.NoArgsConstructor;

/**
 * 类扫描工具类
 */
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class KClassScanner {

	/** 日志对象 */
	private static final Log LOGGER = Logger.getLog(KClassScanner.class);
	/** CLASS */
	private static final String CLASS = ".class";

	/**
	 * 扫描类
	 * @param packageName 待扫描包名
	 * @param inJar 是否扫描JAR包
	 * @param scanSubPackage 是否需要扫描子包
	 * @param annotations 只扫描带有注解类的类
	 * @return 类列表
	 */
	@SafeVarargs
	public static List<Class<?>> scanClasses(final String packageName, final boolean inJar, final boolean scanSubPackage, final Class<? extends Annotation>... annotations) {
		final List<Class<?>> classes = new ArrayList<>();
		final String path = packageName.replaceAll("\\.", "/");
		final ClassLoader classLoader = KClassScanner.class.getClassLoader();
		Enumeration<URL> resources = null;
		try {
			resources = classLoader.getResources(path);
		} catch (final IOException e) {
			throw new KRuntimeException(e);
		}
		for (final Enumeration<URL> iterator = resources; iterator.hasMoreElements();) {
			final URL url = iterator.nextElement();
			switch (url.getProtocol()) {
				case "file":
					URI uri = null;
					try {
						uri = url.toURI();
					} catch (final URISyntaxException e) {// never happens
						throw new KRuntimeException(e, ErrorCodeX.NeverHappens);
					}
					scanClassesInFilePath(Paths.get(uri), classes, packageName, scanSubPackage, annotations);
					break;
				case "jar":
					if (!inJar) { break; }
					URLConnection urlConnection;
					try {
						urlConnection = url.openConnection();
					} catch (final IOException e) {// never happens
						throw new KRuntimeException(e, ErrorCodeX.NeverHappens);
					}
					JarFile jarFile = null;
					try {
						jarFile = ((JarURLConnection) urlConnection).getJarFile();
					} catch (final IOException e) {// never happens
						throw new KRuntimeException(e, ErrorCodeX.NeverHappens);
					}
					scanClassesInJar(jarFile, classes, packageName, scanSubPackage, annotations);
					break;
				default :
					break;
			}
		}
		return classes;
	}

	@SafeVarargs
	@SuppressWarnings("resource")
	private static void scanClassesInFilePath(final Path path, final List<Class<?>> classes, final String packageName, final boolean scanSubPackage, final Class<? extends Annotation>... annotations) {
		if (Files.isDirectory(path)) {// 是目录则遍历目录下所有文件
			Stream<Path> stream = null;
			try {
				stream = Files.list(path);
			} catch (final IOException e) {// never happens
				throw new KRuntimeException(e, ErrorCodeX.NeverHappens);
			}
			for (final Iterator<Path> iterator = stream.iterator(); iterator.hasNext();) {
				final Path subPath = iterator.next();
				if (!scanSubPackage && Files.isDirectory(subPath)) { continue; } // 目录直接跳过
				scanClassesInFilePath(subPath, classes, packageName, scanSubPackage, annotations);
			}
		} else {// 是文件则尝试添加
			Path realPath;
			try {
				realPath = path.toRealPath();
			} catch (final IOException e) {// never happens
				throw new KRuntimeException(e, ErrorCodeX.NeverHappens);
			}
			checkToAddClass(realPath.toString(), classes, packageName, scanSubPackage, annotations);
		}
	}

	@SafeVarargs
	private static void scanClassesInJar(final JarFile jarFile, final List<Class<?>> classes, final String packageName, final boolean scanSubPackage, final Class<? extends Annotation>... annotations) {
		for (final Enumeration<JarEntry> entries = jarFile.entries(); entries.hasMoreElements();) {
			final JarEntry jarEntry = entries.nextElement();
			if (!scanSubPackage && jarEntry.isDirectory()) { continue; } // 目录直接跳过
			checkToAddClass(jarEntry.getName(), classes, packageName, scanSubPackage, annotations);
		}
	}

	@SafeVarargs
	private static void checkToAddClass(final String str, final List<Class<?>> classes, final String packageName, final boolean scanSubPackage, final Class<? extends Annotation>... annotations) {
		if (!str.endsWith(CLASS)) { return; } // 非class文件不处理
		final String className = extractClassName(str, packageName);// 从字符串中提取类名
		if (className == null) { return; } // 不在包中不处理
		if (className.contains("$")) { return; } // 代理类或内部类不处理
		if (!scanSubPackage && className.replace(packageName + ".", "").contains(".")) { return; } // 不扫描子包时子包类不处理
		// 记录日志
		if (Configs.FRAMEWORK_DEBUG && LOGGER.isTraceEnabled()) { LOGGER.trace(String.format("checkToAddClass -> checking -> [%s]", className)); }

		// 反射类型
		Class<?> clazz;
		try {
			clazz = Class.forName(className);
		} catch (final ClassNotFoundException e) {// never happens
			throw new KRuntimeException(e, ErrorCodeX.NeverHappens);
		}

		if (KArrayUtils.isEmpty(annotations)) {// 不校验注解直接添加
			classes.add(clazz);
			// 记录日志
			if (Configs.FRAMEWORK_DEBUG && LOGGER.isTraceEnabled()) { LOGGER.trace(String.format("checkToAddClass -> added -> className[%s]", className)); }
			return;
		} else {// 需要校验注解
			for (final Class<? extends Annotation> annotation : annotations) {
				if (clazz.getAnnotation(annotation) != null) {// 包含注解则添加
					classes.add(clazz);
					// 记录日志
					if (Configs.FRAMEWORK_DEBUG && LOGGER.isTraceEnabled()) { LOGGER.trace(String.format("checkToAddClass -> added -> className[%s]", className)); }
					return;
				}
			}
		}
		// 记录日志
		if (Configs.FRAMEWORK_DEBUG && LOGGER.isTraceEnabled()) { LOGGER.trace(String.format("checkToAddClass -> ignored -> [%s]", className)); }
	}

	private static String extractClassName(String str, final String packageName) {
		str = KPathUtils.path2package(str);// 先将路径分隔符转换为包分隔符
		if (!str.contains(packageName)) { return null; } // 不在包中则返回null
		str = str.substring(str.lastIndexOf(packageName));// 在包中则从包中取出
		if (str.endsWith(".class")) { str = str.substring(0, str.length() - ".class".length()); } // 去除后缀
		return str;
	}

	/**
	 * 扫描类
	 * @param <T> 泛型
	 * @param clazz 类型（继承的父类或实现的接口）
	 * @param packageName 待扫描包名
	 * @param inJar 是否扫描JAR包
	 * @param scanSubPackage 是否需要扫描子包
	 * @return 类列表
	 */
	@Tester("com.kinyx.framework.reflect.KClassScanner_Tester_scanClasses")
	@SuppressWarnings("unchecked")
	public static <T> List<Class<? extends T>> scanClasses(final Class<T> clazz, final String packageName, final boolean inJar, final boolean scanSubPackage) {
		final List<Class<? extends T>> list = new ArrayList<>();
		if (clazz.isInterface()) {
			scanClasses(packageName, inJar, scanSubPackage).forEach(cls -> {
				if (KClassUtils.checkImplements(cls, clazz)) {
					list.add((Class<? extends T>) cls);
				}
			});
		} else {
			scanClasses(packageName, inJar, scanSubPackage).forEach(cls -> {
				if (KClassUtils.checkExtends(cls, clazz)) {
					list.add((Class<? extends T>) cls);
				}
			});
		}
		return list;
	}

}
